// TODO: CODE REVIEW & CLEANUP!
//
// TouchLevel.cs
//
// A control for displaying a level.
//

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Threading;
using System.Xml;
using HomeUX.ClientControlSupport;
using HomeUX.Synchronization;
using HomeUX.UiUtilities;
using SyncPathClass = HomeUX.Synchronization.SyncPath;

namespace HomeUX.Controls
{

/// <summary>
/// TODO
/// </summary>
///
[TemplatePart(Name = LayoutRootName, Type = typeof(Panel))]
[TemplatePart(Name = TouchFieldName, Type = typeof(TouchField))]
[TemplatePart(Name = MeterLeftColumnName, Type = typeof(ColumnDefinition))]
[TemplatePart(Name = MeterRightColumnName, Type = typeof(ColumnDefinition))]
[TemplatePart(Name = MeterTextBlockName, Type = typeof(TextBlock))]
[DebuggerDisplay("TouchLevel {Label}")]
public class TouchLevel : Control, IControlHelperConnector
{
    const string LayoutRootName = "LayoutRoot";
    const string TouchFieldName = "TouchField";
    const string MeterLeftColumnName = "MeterLeftColumn";
    const string MeterRightColumnName = "MeterRightColumn";
    const string MeterTextBlockName = "MeterTextBlock";

    Panel _layoutRoot;
    TouchField _touchField;
    ColumnDefinition _meterLeftColumn;
    ColumnDefinition _meterRightColumn;
    TextBlock _meterTextBlock;

	internal int MaxFavorites { get; private set; }

    /// <summary>
    /// Implements the connection between this control and the global SyncTree object, which in
    /// turn manages the connection to the server.
    /// </summary>
    SyncHelper _syncHelper;

    public static readonly DependencyProperty LabelProperty =
        DependencyProperty.Register("Label", typeof(object), typeof(TouchLevel), null);
    public static readonly DependencyProperty LevelProperty =
        DependencyProperty.Register("Level", typeof(double), typeof(TouchLevel),
        new PropertyMetadata(OnLevelPropertyChanged));
    public static readonly DependencyProperty IsPopupOpenProperty = DependencyProperty.Register("IsPopupOpen",
        typeof(bool), typeof(TouchLevel),
        new PropertyMetadata(TouchLevel.OnIsPopupOpenPropertyChanged));
    public static readonly DependencyProperty IsWaitingProperty =
        DependencyProperty.Register("IsWaiting", typeof(bool), typeof(TouchLevel),
            new PropertyMetadata(TouchLevel.OnIsWaitingPropertyChanged));
    public static readonly DependencyProperty SyncPathProperty =
        DependencyProperty.Register("SyncPath", typeof(string), typeof(TouchLevel),
            new PropertyMetadata(TouchLevel.OnSyncPathPropertyChanged));

    public event EventHandler PopupOpened;
    public event EventHandler PopupClosed;
    public event LevelChangedEventHandler LevelChanged;
    public event IsWaitingChangedEventHandler IsWaitingChanged;

    public List<double> _favorites = new List<double>();

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public ICollection<double> Favorites
    {
        get
        {
            return new ReadOnlyCollection<double>(_favorites);
        }
        set
        {
            SetFavorites(value, true);
        }
    }

    /// <summary>
    /// Updates <r>Favorites</r>, and optionally broadcasts the update to listeners on this and
    /// other clients.
    /// </summary>
    ///
    /// <param name="newValue">The new value for <r>Favorites</r>.</param>
    ///
    /// <param name="broadcastChange">If <n>true</n>, the change is broadcast to listeners on this
    ///     and other clients.  Use <n>false</n> if this update is in response to reception of
    ///     such a broadcast.</param>
    ///
    public void SetFavorites(ICollection<double> newValue, bool broadcastChange)
    {
        _favorites = new List<double>(newValue
            .Take(MaxFavorites)
            .Select(x => TouchLevel.QuantizeAndBoundLevelValue(x))
            .Distinct()
            .OrderBy(x => x));
        if (FavoritesChanged != null)
            FavoritesChanged(this, EventArgs.Empty);
        if (broadcastChange && _syncHelper.IsPersonalizationNodeAvailable)
        {
            var favoritesString = new StringBuilder(100);
            foreach (double favorite in _favorites)
            {
                if (favoritesString.Length > 0)
                    favoritesString.Append(',');
                favoritesString.Append(XmlConvert.ToString(favorite));
            }
            GetFavoritesNode().SetValue(favoritesString.ToString(), this);
        }
    }

    public event EventHandler FavoritesChanged;

    public object Label
    {
        get
        {
            return (object)GetValue(LabelProperty);
        }
        set
        {
            SetValue(LabelProperty, value);
        }
    }

    public double Level
    {
        get
        {
            return (double)GetValue(LevelProperty);
        }
        set
        {
            SetValue(LevelProperty, QuantizeAndBoundLevelValue(value));
        }
    }

    public bool IsPopupOpen
    {
        get
        {
            return (bool)GetValue(IsPopupOpenProperty);
        }
        set
        {
            SetValue(IsPopupOpenProperty, value);
        }
    }

    public bool IsWaiting
    {
        get
        {
            return (bool)GetValue(IsWaitingProperty);
        }
        set
        {
            SetValue(IsWaitingProperty, value);
        }
    }

    public string SyncPath
    {
        get
        {
            return (string)GetValue(SyncPathProperty);
        }
        set
        {
            SetValue(SyncPathProperty, value);
        }
    }

    public TouchLevel()
    {
        DefaultStyleKey = typeof(TouchLevel);
        _syncHelper = new SyncHelper(this, SyncHelperFeatures.Personalization);

        // hook up <_syncHelper> events
        _syncHelper.IsOnlineChanged += _syncHelper_IsOnlineChanged;
        _syncHelper.IsWaitingChanged += _syncHelper_IsWaitingChanged;
        _syncHelper.PrimaryValueChanged += _syncHelper_PrimaryValueChanged;
        _syncHelper.PersonalizationChanged += _syncHelper_PersonalizationChanged;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        ControlHelper.GetRequiredTemplateChild(this, LayoutRootName, out _layoutRoot);
        ControlHelper.GetRequiredTemplateChild(this, TouchFieldName, out _touchField);
        ControlHelper.GetRequiredTemplateChild(this, MeterLeftColumnName, out _meterLeftColumn);
        ControlHelper.GetRequiredTemplateChild(this, MeterRightColumnName, out _meterRightColumn);
        ControlHelper.GetRequiredTemplateChild(this, MeterTextBlockName, out _meterTextBlock);

        int maxFavorites;
        UiUtil.GetRequiredResource(_layoutRoot, "MaxFavorites", out maxFavorites);
        MaxFavorites = maxFavorites;

        GotFocus += TouchLevel_GotFocus;
        LostFocus += TouchLevel_LostFocus;
        KeyDown += TouchLevel_KeyDown;

        _layoutRoot.MouseLeftButtonDown += LayoutRoot_MouseLeftButtonDown;
        _layoutRoot.GotFocus += LayoutRoot_GotFocus;
        _layoutRoot.LostFocus += LayoutRoot_LostFocus;

        // check if this control is an orphan when Parent may change
        //_syncHelper.CheckOrphanStatus(Parent);
        LayoutUpdated += (sender, e) => _syncHelper.CheckOrphanStatus(Parent);

        // initialize UI based on initial property values
        UpdateVisualState();
    }

    /// <summary>
    /// Called when <r>_syncHelper</r>.<s>SyncHelper.PrimaryNode</s>.<s>SyncNodeBase.Value</s> has changed -- for example, if another client has
    /// changed the value.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void _syncHelper_PrimaryValueChanged(object sender, ValueChangedEventArgs e)
    {
        // ignore this event if the change originated from this control
        if (e.Originator == this)
            return;

        // Ignore this event if an hourglass is displayed, to deal with this scenario:
        //   Imagine the user is dragging a sound volume slider, but the audio hardware is
        //   slow to respond, so one or more volume changes get queued up.  So this is what
        //   the user sees: they drag the slider from (e.g.) top (100%) to bottom (0%), and
        //   then over the next couple of seconds they see the slider jump back up to 75%, then
        //   down to 50%, then down to 25%, then to 0% (for example).  This is disconcerting.
        if (_syncHelper.IsWaiting)
            return;

        // apply the new value in <_syncHelper.PrimaryNode.Value> to <Level>
        object newValue = _syncHelper.PrimaryNode.Value;
        if (newValue is double)
            Level = (double)newValue;
    }

    /// <summary>
    /// Called when a descendant of <r>_syncHelper</r>.<s>SyncHelper.PersonalizationNode</s> has changed -- for
    /// example, if another client has change the personalization information.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void _syncHelper_PersonalizationChanged(object sender, DescendantChangedEventArgs e)
    {
        // ignore this event if the change originated from this control
        if (e.Originator == this)
            return;

        var favoritesString = GetFavoritesNode().Value as string;
        if (favoritesString == null)
            return;

        string[] favoriteStrings = favoritesString.Split(new char[] { ',' },
            StringSplitOptions.RemoveEmptyEntries);
        List<double> favorites = new List<double>(favoriteStrings.Length);
        foreach (string favoriteString in favoriteStrings)
        {
            try
            {
                favorites.Add(XmlConvert.ToDouble(favoriteString));
            }
            catch (FormatException)
            {
                return;
            }
            catch (OverflowException)
            {
                return;
            }
        }

        SetFavorites(favorites, false);
    }

    /// <summary>
    /// Called when <r>_syncHelper</r>.<s>SyncHelper.IsWaiting</s> has changed.  This occurs when
    /// <r>_syncHelper</r>.<s>SyncHelper.PrimaryNode</s>.<s>SyncNodeBase.Value</s> is changed on this client, but the change has not yet been
    /// reflected on the server.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void _syncHelper_IsWaitingChanged(object sender, EventArgs e)
    {
        // show or hide the wait icon (e.g. hourglass)
        IsWaiting = _syncHelper.IsWaiting;
    }

    /// <summary>
    /// Called when <r>_syncHelper</r><s>SyncHelper.IsOnline</s> has changed.  This occurs when the connection to
    /// the server has been broken or reestablished.  This also occurs when
    /// <r>_syncHelper</r>.<s>SyncHelper.PrimaryNode</s>.<s>SyncNodeBase.Value</s> becomes <n>null</n> (indicating, for example, that the device
    /// associated with that value is offline) or becomes non-<n>null</n>.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void _syncHelper_IsOnlineChanged(object sender, EventArgs e)
    {
        // enable or disable the control
        IsEnabled = _syncHelper.IsOnline;
    }

    void TouchLevel_GotFocus(object sender, RoutedEventArgs e)
    {
        _touchField.DisplayAsFocused = true;
    }

    void TouchLevel_LostFocus(object sender, RoutedEventArgs e)
    {
        _touchField.DisplayAsFocused = false;
    }

    void TouchLevel_KeyDown(object sender, KeyEventArgs e)
    {
        if (!e.Handled)
        {
            e.Handled = OnKeyDown(e.Key);
        }
    }

    internal bool OnKeyDown(Key key)
    {
        switch (key)
        {

        case Key.Enter:
        case Key.Space:

            IsPopupOpen = !IsPopupOpen;
            break;

        case Key.Left:
        case Key.Down:

            Level -= 0.01;
            break;

        case Key.Right:
        case Key.Up:

            Level += 0.01;
            break;

        case Key.PageDown:

            Level -= 0.05;
            break;

        case Key.PageUp:

            Level += 0.05;
            break;

        case Key.Home:

            Level = 1;
            break;

        case Key.End:

            Level = 0;
            break;

        default:

            int digit = key - Key.D0;
            if ((digit > 0) && (digit <= _favorites.Count))
            {
                // select the favorite specified by the digit key pressed
                Level = _favorites[digit - 1];
                IsPopupOpen = false;
                break;
            }
            else
                return false;
        }

        return true;
    }

    void LayoutRoot_GotFocus(object sender, RoutedEventArgs e)
    {
        ShowFocus();
    }

    void LayoutRoot_LostFocus(object sender, RoutedEventArgs e)
    {
        ShowFocus();
    }

    TouchLevelPopup _popup;

    void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        IsPopupOpen = !IsPopupOpen;
    }

    void OnPopupClosed()
    {
		_popup = null;
		IsPopupOpen = false;
        _touchField.IsLit = false;
        if (PopupClosed != null)
            PopupClosed(this, EventArgs.Empty);
    }

    void OnIsPopupOpenPropertyChanged(bool isPopupOpen)
    {
        if (isPopupOpen)
        {
			if (_popup == null)
			{
                _popup = new TouchLevelPopup(this)
                {
                    Background = this.Background
                };
                ClientUtil.ShowDialog(this, _popup, new DialogSettings()
                {
                    PeekThrough = this,
                    OnClose = OnPopupClosed
                });
                _touchField.IsLit = true;
                if (PopupOpened != null)
                    PopupOpened(this, EventArgs.Empty);
			}
        }
        else
		{
			if (_popup != null)
			{
                ClientUtil.CloseDialog(_popup, true);
				_popup = null;
            }
		}
    }

    static void OnIsPopupOpenPropertyChanged(DependencyObject d,
		DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchLevel)d;
        target.OnIsPopupOpenPropertyChanged((bool)e.NewValue);
    }

    void ShowFocus()
    {
        var focusedElement = FocusManager.GetFocusedElement();
        var textBox = focusedElement as TextBox;
        string text;
        if (textBox != null)
            text = textBox.Text;
        else
            text = "N/A";
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        Size size = base.ArrangeOverride(finalSize);

        return size;
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        return base.MeasureOverride(availableSize);
    }

    static void OnLevelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((TouchLevel)d).OnLevelPropertyChanged((double) e.OldValue, (double) e.NewValue);
    }


    static double QuantizeAndBoundLevelValue(double value)
    {
        QuantizeAndBoundLevelValueToInt(ref value);
        return value;
    }

    static int QuantizeAndBoundLevelValueToInt(ref double value)
    {
        int percent = Math.Min(Math.Max((int)Math.Round(value * 100), 0), 100);
        value = ((double)percent) / 100;
        return percent;
    }

    void OnLevelPropertyChanged(double oldValue, double newValue)
    {
        int newPercent = QuantizeAndBoundLevelValueToInt(ref newValue);
        Level = newValue;
        if (_layoutRoot != null) // i.e. if OnApplyTemplate called already
        {
            if (_syncHelper.IsPrimaryNodeAvailable)
                _syncHelper.PrimaryNode.SetValue(newValue, this);
            UpdateVisualState(newPercent);
            if (LevelChanged != null)
            {
                LevelChanged(this, new LevelChangedEventArgs()
                {
                    OldValue = oldValue,
                    NewValue = newValue
                });

            }
        }
    }

    void UpdateVisualState()
    {
        double level = Level;
        UpdateVisualState(QuantizeAndBoundLevelValueToInt(ref level));
    }

    void UpdateVisualState(int percent)
	{
        _meterLeftColumn.Width = new GridLength(percent, GridUnitType.Star);
        _meterRightColumn.Width = new GridLength(100 - percent, GridUnitType.Star);
        _meterTextBlock.Text = String.Format("{0}%", percent);

        //_touchField.IsWaiting = (IsWaiting || (_pendingUpdatesCount > 0));
	}

    static void OnIsWaitingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchLevel)d;
		//target.UpdateVisualState();
        target._touchField.IsWaiting = (bool)e.NewValue;

        // fire the IsWaitingChanged event
        if (target.IsWaitingChanged != null)
        {
            target.IsWaitingChanged(target, new IsWaitingChangedEventArgs()
            {
                IsWaiting = (bool)e.NewValue
            });
        }
    }

    static void OnSyncPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchLevel)d;
        target._syncHelper.SyncPath = SyncPathClass.FromString((string)e.NewValue);
    }

    SyncNodeBase GetFavoritesNode()
    {
        return _syncHelper.PersonalizationNode.CreateNode(SyncPathClass.Build("Favorites"));
    }

    Control IControlHelperConnector.GetControl()
    {
        return this;
    }

    DependencyObject IControlHelperConnector.GetTemplateChild(string childName)
    {
        return GetTemplateChild(childName);
    }
}

[TemplatePart(Name = LayoutRootName, Type = typeof(Panel))]
[TemplatePart(Name = FavoritesPanelName, Type = typeof(Panel))]
[TemplatePart(Name = MainPanelName, Type = typeof(Panel))]
[TemplatePart(Name = FavoriteButtonParentName, Type = typeof(Panel))]
[TemplatePart(Name = Down5ButtonName, Type = typeof(ButtonBase))]
[TemplatePart(Name = Up5ButtonName, Type = typeof(ButtonBase))]
[TemplatePart(Name = Down1ButtonName, Type = typeof(ButtonBase))]
[TemplatePart(Name = Up1ButtonName, Type = typeof(ButtonBase))]
[TemplatePart(Name = SaveFavoriteButtonName, Type = typeof(ButtonBase))]
[TemplatePart(Name = DeleteFavoritesButtonName, Type = typeof(TouchButton))]
[TemplatePart(Name = DeleteFavoritesModeHelpName, Type = typeof(UIElement))]
[TemplateVisualState(GroupName = FavoritesPanelStatesName, Name = FavoritesPanelVisibleName)]
[TemplateVisualState(GroupName = FavoritesPanelStatesName, Name = FavoritesPanelHiddenName)]
[TemplateVisualState(GroupName = FavoritesPanelStatesName, Name = FavoritesPanelVisibleUpsideDownName)]
[TemplateVisualState(GroupName = FavoritesPanelStatesName, Name = FavoritesPanelHiddenUpsideDownName)]
[TemplateVisualState(GroupName = DeleteFavoritesModeStatesName, Name = DeleteFavoritesModeOffName)]
[TemplateVisualState(GroupName = DeleteFavoritesModeStatesName, Name = DeleteFavoritesModeOnName)]
public class TouchLevelPopup : Control, IControlHelperConnector, INotifyDialog
{
    const string LayoutRootName = "LayoutRoot";
    const string FavoritesPanelName = "FavoritesPanel";
    const string MainPanelName = "MainPanel";
    const string FavoriteButtonParentName = "FavoriteButtonParent";
    const string Down5ButtonName = "Down5Button";
    const string Up5ButtonName = "Up5Button";
    const string Down1ButtonName = "Down1Button";
    const string Up1ButtonName = "Up1Button";
    const string SaveFavoriteButtonName = "SaveFavoriteButton";
    const string DeleteFavoritesButtonName = "DeleteFavoritesButton";
    const string DeleteFavoritesModeHelpName = "DeleteFavoritesModeHelp";

    const string FavoritesPanelStatesName = "FavoritesPanelStates";
    const string FavoritesPanelVisibleName = "FavoritesPanelVisible";
    const string FavoritesPanelHiddenName = "FavoritesPanelHidden";
    const string FavoritesPanelVisibleUpsideDownName = "FavoritesPanelVisible_UpsideDown";
    const string FavoritesPanelHiddenUpsideDownName = "FavoritesPanelHidden_UpsideDown";

    const string DeleteFavoritesModeStatesName = "DeleteFavoritesModeStates";
    const string DeleteFavoritesModeOffName = "DeleteFavoritesModeOff";
    const string DeleteFavoritesModeOnName = "DeleteFavoritesModeOn";

    const string FavoriteButtonStyleName = "FavoriteButtonStyle";

    Panel _layoutRoot;
    Panel _favoritesPanel;
    Panel _mainPanel;
    Panel _favoriteButtonParent;
    ButtonBase _down5Button;
    ButtonBase _up5Button;
    ButtonBase _down1Button;
    ButtonBase _up1Button;
    ButtonBase _saveFavoriteButton;
    TouchButton _deleteFavoritesButton;
    UIElement _deleteFavoritesModeHelp;
    
    VisualStateGroup _favoritesPanelStates;

    Style _favoriteButtonStyle;

    TouchLevel _touchLevel;
    bool _favoritesPanelIsVisible = true;
    bool _dialogIsAbovePeekThroughControl;
    bool _deleteFavoritesMode; // mode where user is selecting a favorite button to delete

    public static readonly DependencyProperty UpsideDownCanvasTopProperty =
        DependencyProperty.RegisterAttached("UpsideDownCanvasTop", typeof(double),
        typeof(TouchLevelPopup), null);

    public TouchLevelPopup(TouchLevel touchLevel)
    {
        _touchLevel = touchLevel;
        _touchLevel.LevelChanged += _touchLevel_LevelChanged;
        DefaultStyleKey = typeof(TouchLevelPopup);
        Loaded += TouchLevelPopup_Loaded;
        KeyDown += TouchLevelPopup_KeyDown;
    }

    void _touchLevel_LevelChanged(object sender, LevelChangedEventArgs e)
    {
        OnLevelChanged(e.NewValue);
    }

    // called when this TouchLevelPopup opens, and whenever _touchLevel.Level changes
    void OnLevelChanged(double newLevel)
    {
        // setting the level may affect whether the "Save Favorite" button should be enabled
        UpdateSaveFavoriteEnabledState();

        // enable/disable other buttons
        _down5Button.IsEnabled = (newLevel > 0);
        _down1Button.IsEnabled = (newLevel > 0);
        _up5Button.IsEnabled = (newLevel < 1);
        _up1Button.IsEnabled = (newLevel < 1);
    }

    void UpdateSaveFavoriteEnabledState()
    {
        // if the current level is already a favorite button, disable "Save Favorite"
        _saveFavoriteButton.IsEnabled = !_touchLevel.Favorites.Contains(_touchLevel.Level)
            && (_touchLevel.Favorites.Count < _touchLevel.MaxFavorites);
    }

    void TouchLevelPopup_KeyDown(object sender, KeyEventArgs e)
    {
        if (!e.Handled)
        {
			if (e.Key == Key.S)
				SaveFavorite();
			else
            if (e.Key != Key.Space)
                e.Handled = _touchLevel.OnKeyDown(e.Key);
        }
    }

    public static double GetUpsideDownCanvasTop(FrameworkElement element)
    {
        return (double) element.GetValue(UpsideDownCanvasTopProperty);
    }

    public static void SetUpsideDownCanvasTop(FrameworkElement element, double value)
    {
        element.SetValue(UpsideDownCanvasTopProperty, value);
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        ControlHelper.GetRequiredTemplateChild(this, LayoutRootName, out _layoutRoot);
        ControlHelper.GetRequiredTemplateChild(this, FavoritesPanelName, out _favoritesPanel);
        ControlHelper.GetRequiredTemplateChild(this, MainPanelName, out _mainPanel);
        ControlHelper.GetRequiredTemplateChild(this, FavoriteButtonParentName, out _favoriteButtonParent);
        ControlHelper.GetRequiredTemplateChild(this, Down5ButtonName, out _down5Button);
        ControlHelper.GetRequiredTemplateChild(this, Up5ButtonName, out _up5Button);
        ControlHelper.GetRequiredTemplateChild(this, Down1ButtonName, out _down1Button);
        ControlHelper.GetRequiredTemplateChild(this, Up1ButtonName, out _up1Button);
        ControlHelper.GetRequiredTemplateChild(this, SaveFavoriteButtonName, out _saveFavoriteButton);
        ControlHelper.GetRequiredTemplateChild(this, DeleteFavoritesButtonName, out _deleteFavoritesButton);
        ControlHelper.GetRequiredTemplateChild(this, DeleteFavoritesModeHelpName, out _deleteFavoritesModeHelp);

        ControlHelper.GetRequiredTemplateChild(this, FavoritesPanelStatesName, out _favoritesPanelStates);

        _favoritesPanelStates.CurrentStateChanged +=
            delegate(object sender, VisualStateChangedEventArgs e)
            {
                _favoritesPanelIsVisible =
                    (e.NewState.Name == FavoritesPanelVisibleName) ||
                    (e.NewState.Name == FavoritesPanelVisibleUpsideDownName);
            };

        _down5Button.Click += delegate
        {
            // leave delete-favorites mode, if we're in it
            if (EnterDeleteFavoritesMode(false))
                return;

            // decrease the level by up to 5%
            _touchLevel.Level -= 0.05;
        };

        _up5Button.Click += delegate
        {
            // leave delete-favorites mode, if we're in it
            if (EnterDeleteFavoritesMode(false))
                return;

            // increase the level by up to 5%
            _touchLevel.Level += 0.05;
        };

        _down1Button.Click += delegate
        {
            // leave delete-favorites mode, if we're in it
            if (EnterDeleteFavoritesMode(false))
                return;

            // decrease the level by up to 1%
            _touchLevel.Level -= 0.01;
        };

        _up1Button.Click += delegate
        {
            // leave delete-favorites mode, if we're in it
            if (EnterDeleteFavoritesMode(false))
                return;

            // increase the level by up to 1%
            _touchLevel.Level += 0.01;
        };

        _saveFavoriteButton.Click += delegate { SaveFavorite(); };

        _deleteFavoritesButton.Click += delegate
        {
            // enter delete-favorites mode, or leave that mode if we're currently in it
            EnterDeleteFavoritesMode(!_deleteFavoritesMode);
        };

        _deleteFavoritesModeHelp.MouseLeftButtonDown += delegate
        {
            // the user leave delete-favorites mode, if we're in it
            EnterDeleteFavoritesMode(false);
        };

        UiUtil.GetRequiredResource(_layoutRoot, FavoriteButtonStyleName, out _favoriteButtonStyle);

        UpdateFavoritesPanel();

        OnLevelChanged(_touchLevel.Level);
    }

    void SaveFavorite()
    {
        // leave delete-favorites mode, if we're in it
        if (EnterDeleteFavoritesMode(false))
            return;

        // add <_touchLevel.Level> as a favorite (if it's not already a favorite)
        var favorites = new List<double>(_touchLevel.Favorites);
        favorites.Add(_touchLevel.Level);
        _touchLevel.Favorites = favorites;
        UpdateFavoritesPanel();
        UpdateSaveFavoriteEnabledState();
    }

    // updates Favorites panel based on <_touchLevel.Favorites>
    void UpdateFavoritesPanel()
    {
        _favoriteButtonParent.Children.Clear();
        foreach (double favorite in _touchLevel.Favorites)
        {
            if (_favoriteButtonParent.Children.Count >= _touchLevel.MaxFavorites)
                break;
            var favoriteButton = new TouchButton()
            {
                Content = String.Format("{0:n0}%", favorite * 100),
                Style = _favoriteButtonStyle,
                SuppressIsWaitingDisplay = true,
                ItemAction = ItemAction.SetNumericValue
            };
            Item.SetUntypedValue(favoriteButton, XmlConvert.ToString(favorite));
            favoriteButton.SyncPath = _touchLevel.SyncPath; // must set after ItemAction and Data
            favoriteButton.Click += delegate
            {
                if (_deleteFavoritesMode)
                {
                    // we're in delete-favorites mode -- delete this button
                    var favorites = new List<double>(_touchLevel.Favorites);
                    double level = XmlConvert.ToDouble(Item.GetUntypedValue(favoriteButton));
                    int index = favorites.IndexOf(level);
                    favorites.RemoveAt(index);
                    _touchLevel.Favorites = favorites;
                    UpdateFavoritesPanel();
                    UpdateSaveFavoriteEnabledState();

                    // if that was the last favorite button, leave delete-favorites mode;
                    // if not, set focus to adjacent favorite button
                    int count = _favoriteButtonParent.Children.Count;
                    if (count == 0)
                        EnterDeleteFavoritesMode(false);
                    else
                    {
                        if (index >= count)
                            index--;
                        ((Control)_favoriteButtonParent.Children[index]).Focus();
                    }
                }
                else
                {
                    // the ItemAction on the button (which executes before the Click event is
                    // fired) already caused Level to be updated -- so we just need to close the
                    // dialog
                    _touchLevel.IsPopupOpen = false;
                }
            };
            _favoriteButtonParent.Children.Add(favoriteButton);
        }
        ShowFavoritesPanel(_touchLevel.Favorites.Count > 0);
    }

    void INotifyDialog.OnDialogPosition(bool isAbovePeekThroughControl)
    {
        _dialogIsAbovePeekThroughControl = isAbovePeekThroughControl;
        UpdateVisualState(false, false);
    }

    void INotifyDialog.OnDialogClose()
    {
        // stop any running animations, so that the "onCompleted" delegates used in any
        // ControlHelper.GoToVisualState calls won't be called after this dialog closes
        ClientUtil.StopAllStoryboards(_favoritesPanelStates);
		
        // remove the Favorites buttons from the Favorites panel, since these buttons have a
        // connection to the SyncTree, and if we don't remove them that means those buttons,
        // and this entire control, won't get garbage-collected
        _favoriteButtonParent.Children.Clear();

        // remove any other references to this object (again, to avoid blocking garbage
        // collection)
        _touchLevel.LevelChanged -= _touchLevel_LevelChanged;
    }

    void ShowFavoritesPanel(bool show)
    {
        // do nothing if the current state of the Favorites panel is already the desired state
        if (show == _favoritesPanelIsVisible)
            return;
        _favoritesPanelIsVisible = show;

        UpdateVisualState(true, true);
    }

    bool EnterDeleteFavoritesMode(bool enter)
    {
        // do nothing if we're already in the desired state
        if (_deleteFavoritesMode == enter)
            return false; // state didn't change

        VisualStateManager.GoToState(this,
            enter ? DeleteFavoritesModeOnName : DeleteFavoritesModeOffName, true);

        // workaround for bug: "<ObjectAnimationUsingKeyFrames>" doesn't work here
        _deleteFavoritesButton.IsLit = enter;

        foreach (TouchButton button in _favoriteButtonParent.Children)
            button.ItemAction = enter ? ItemAction.None : ItemAction.SetNumericValue;

        _deleteFavoritesMode = enter;

        // if we're entering delete-favorites mode, set focus on the first favorite
        if (enter)
            ((Control)_favoriteButtonParent.Children[0]).Focus();

        return true; // state changed
    }

    void UpdateVisualState(bool useTransitions, bool repositionDialog)
    {
        if (repositionDialog)
        {
            // remove the glow around the dialog
            ClientUtil.RemoveGlowAroundDialog(this);
        }

        // transition to the new state; when the transition is complete, move the dialog to its
        // new position and restore the glow around the dialog
        string newState;
        if (_dialogIsAbovePeekThroughControl)
        {
            newState = _favoritesPanelIsVisible ? FavoritesPanelVisibleUpsideDownName :
                FavoritesPanelHiddenUpsideDownName;
        }
        else
        {
            newState = _favoritesPanelIsVisible ? FavoritesPanelVisibleName : FavoritesPanelHiddenName;
        }
        ControlHelper.GoToVisualState(this, newState, useTransitions, delegate
        {
            if (repositionDialog)
                ClientUtil.RepositionDialog(this);
        });


        _deleteFavoritesButton.IsEnabled = _favoritesPanelIsVisible;
    }

    void TouchLevelPopup_Loaded(object sender, RoutedEventArgs e)
    {
        Control control = null;
        if (_favoriteButtonParent.Children.Count > 0)
            control = _favoriteButtonParent.Children[0] as Control;
        if (control == null)
            control = _down5Button;
        Dispatcher.BeginInvoke(delegate { control.Focus(); });
    }

    Control IControlHelperConnector.GetControl()
    {
        return this;
    }

    DependencyObject IControlHelperConnector.GetTemplateChild(string childName)
    {
        return GetTemplateChild(childName);
    }
}

public class LevelChangedEventArgs : RoutedEventArgs
{
	public double OldValue { get; internal set; }
	public double NewValue { get; internal set; }
}

public delegate void LevelChangedEventHandler(Object sender, LevelChangedEventArgs e);

}
